vlwkaos' digital garden

우아한 형제들 타입 스크립트 세미나

2021-05-15

TypeScript 공식 문서가 갱신 되었다. 조건부 type등 다양한 자료가 생겼으니 찾아보는 것도 좋을 듯.


2020-08-26 정리되지 않은 노트

자료


목표

  • 타입 시스템 올바르게 사용하는 법
  • 실전 타입 스크립트 코드 작성하기

TypeScript로 타이핑을 잘하면 런타임 전에 미리 알 수 있는 오류도 있다.

타입 시스템

  1. 컴파일러에게 명시적으로 타입을 알려줌
  2. 컴파일러가 자동으로 타입을 추론

타입 스크립트는 둘다.


1.작성자와 사용자

구현자가 만든 함수를 사용자가 형태를 보고 사용 한다. 타입 = 변수가 할 수 있는 일을 정의

사용자는 함수의 기능을 판단할 때

  • 코드 읽기
  • 함수의 이름으로 부터 유추 시간이 많이 듦

noImplicitAny 옵션을 키면 명시적 any를 사용 (인자를 strict하게) strictNullChecks 기본 null, undefined 를 제거 noImplicitReturn 리턴을 명시적으로, 제대로된 리턴이 없을 때 에러

function f5(a: number): number{
    if (a > 0)
        return a * 38;
    // return ?
}

2.interface와 type alias

오브젝트 구조를 잡는 두가지 방법 Interface방식과 type 방식

interface PersonInterface {
}

type PersonType = {
    name: string;
}

읽어보기: [[Structural vs Nominal Subtyping 그리고 TypeScript]]

type PersonID = string & { readonly brand: unique symbol}

이렇게 intersection 시킬 수 있음

function getPersonById(id: PersonID): PersonID {
    return id as PersonID;
}
function getPersonById() {}
getPersonById(PersonID('id-aaaa'));

읽어보기: 자주 쓰는 타입

Intersection types

두개의 interface를 & 로 합침, extends A,B

Union types

type ID = number | string;
type Animal = Bird | Fish;

Declaration Merging

  • 인터페이스는 같은 이름을 가지면 합쳐짐
  • 타입 선언은 합칠 수 없음

3. 서브타입과 슈퍼 타입

타입이 같거나 서브 타입인 경우 할당이 가능.


// 투플 선언법
let sub3: [number, number];

// 서브타입 할당
let sub6: SubDog = new SubDog();
let sup6: Animal = sub6
// sub1타입은 sup1타입의 서브 타입
let sub1: 1 = 1;
let sup1: number = sub1;

함수의 매개변수 타입만 같거나 슈퍼 타입인 경우, 할당이 가능

class Person {}
class Developer extends Person {
  coding() {}
}
class StartupDeveloper extends Developer {
  burning() {}
}

function tellme(f: (d: Developer) => Developer) {}

// Developer => Developer 에다가 Developer => Developer 를 할당하는 경우
tellme(function dToD(d: Developer): Developer {
  return new Developer();
});

// Developer => Developer 에다가 Person => Developer 를 할당하는 경우
tellme(function pToD(d: Person): Developer {
  return new Developer();
});

// Developer => Developer 에다가 StartupDeveloper => Developer 를 할당하는 경우
tellme(function sToD(d: StartupDeveloper): Developer {
  return new Developer();
});

strictFunctionTypes 옵션을 켜면 이외의 경우에 에러

any대신에 unknown을 사용하면? 받는 건 마음대로 인데 함수를 일단 안된다고 보고 type 제한 조건을 if문으로 각각 처리. unknown을 쓰는 것이 안정성을 높인다.


4. 타입 추론 이해하기

let 과 const의 타입 추론

const는 literal type 딱 초기화된 값들만 들어감 let은 type을 가짐

Best Common Type let j = [0,1,null]; // (number | null) 이런식으로 최대한 넓은 범위로 만약 더 strict하고 싶다면 as const를 붙여주는 것이지

부모 타입으로 만들고 싶으면 타입 명시를 하거나 부모가 배열 안에 같이 들어가면 부모로 추론이 된다.

let l = [new Rhino(), new Elephant(), new Snake()]; // (Rhino | Elephant | Snake)[]
const m = [new Rhino(), new Elephant(), new Snake()]; // (Rhino | Elephant | Snake)[]
const n = [new Animal(), new Rhino(), new Elephant(), new Snake()]; // Animal[]
const o: Animal[] = [new Rhino(), new Elephant(), new Snake()]; // Animal[]

Contextual Typing

// Parameter 'e' implicitly has an 'any' type.
const click = (e) => {
  e; // any
};

document.addEventListener('click', click);
document.addEventListener('click', (e) => {
  e; // MouseEvent
});

Type Guard

primitive 타입일 때

function getNumber(value: number | string): number {
  value; // number | string
  if (typeof value === 'number') {
    value; // number
    // 여기서 number를 처리했으므로 Type이 떨어져나간다.
    return value;
  }
  value; // string
  return -1;
}

instanceof Type Guard

Error 객체 구분에 많이 쓰임

class NegativeNumberError extends Error {}

function getNumber(value: number): number | NegativeNumberError { // 이런 식으로
  if (value < 0) return new NegativeNumberError();

  return value;
}

function main() {
  const num = getNumber(-10);

  if (num instanceof NegativeNumberError) {
    return;
  }

  num; // number
}

in operator Type Guard

프로퍼티가 있냐? 없냐? 로 조건

interface Admin {
  id: string;
  role: string:
}

interface User {
  id: string;
  email: string;
}

function redirect(user: Admin | User) {
  if("role" in user) {
    routeToAdminPage(user.role);
  } else {
    routeToHomePage(user.email);
  }
}

프로퍼티가 같고 type이 다른 경우 클래스 내에 type: 'BOAT'; 뭐 이런식으로 아무튼 명시적으로 클래스를 구분할 수 있는 방법을 만들기

custom Type Guard

if (isCar(machine))...

function isCar(arg: any): arg is Car {
    return arg.type === 'CAR';
}

lodash같은거에서도 이런 방식이 많이 쓰인다.


Class 를 안전하게 만들기

3.9.7에서

strictPropertyInitialization을 켜면 어디서든 값이 지정이 되지 않으면 에러 발생

생성자든 어디든 초기화 해줘야됨

class Square4 {
  area: number;
  sideLength: number;

  constructor(sideLength: number) {
    this.sideLength = sideLength;
    this.area = sideLength ** 2;
  }

4.0.2 부터는 생성자에서 선언할 경우 타입 추론이 될 수도 있음

그러나 생성자 밖에서 다른 함수에서 될 경우 추론되지 않음

변수에 !를 붙이면 assertion을 의미한다. constructor와 처음에서 초기화 되지 않지만 어디에서 초기화 될거야라는 뜻

우아한 형제들 타입 스크립트 세미나